/*
 * SIT_Geometry.c : heart of widget layout management.
 *
 * Inspired from XmForm and XmBulletinBoard widget from Motif.
 *
 * Written by T.Pierron, June 2006.
 */

#include "SIT_P.h"
#include <stddef.h>

//#define	DEBUG_GEOM     /* Warning: verbose */

static uint8_t relative[] = {0, 0, 0, 1, 1, 1, 1, 0};

/* Check if widget is trapped between unmovable controls */
static int SIT_CanMoveSub(SIT_Widget w, int side)
{
	static uint8_t states[] = {
		20, 20, 3, 12, 3, 12, 0x84, 0x80, 0x82, 0x82, 0x84, 0x80, 0x84, 0x80, 3, 20
	};
	int dir;
	for (dir = side < 2 ? 2 : -2; 0 <= side && side < 4; )
	{
		SIT_Attach * a = &w->sw_Attachment[side];
		int state = states[(a->sa_Type << 1) | (side >> 1)];
		if (state < 16)
			return a->sa_Type == SITV_AttachPosition && a->sa_Arg < 65536 ? 3 : state;
		if (state == 20) { side += dir; continue; }
		w = (APTR) a->sa_Arg;
		side += state - 0x82;
		dir = side < 2 ? 2 : -2;
	}
	return side < 2 ? 3 : 4;
}

/*
 * Not that trivial to answer. The problem is: we have a widget that given its constraints,
 * is smaller than its sw_OptimalBox size. We need to make room by enlarging the dialog.
 * But to be effective, the control must not be trapped between unmovable constraints (like
 * for example an AttachFixed on the left and right edges). If that's the case, simply let
 * the dialog's size as is.
 */
static Bool SIT_CanMove(SIT_Widget w, int side)
{
	static uint8_t states[] = {
		15, 15, 3, 12, 3, 12, 0x84, 0x80, 0x82, 0x82, 0x84, 0x80, 0x84, 0x80, 20, 20
	};
	int flags;
	for (flags = 0; side < 4; side += 2)
	{
		SIT_Attach * a = &w->sw_Attachment[side];
		int state = a->sa_Type == SITV_AttachPosition && a->sa_Arg < 65536 ? 3 : states[(a->sa_Type << 1) | (side >> 1)];
		if (state & 0x80)
			flags |= SIT_CanMoveSub((APTR)a->sa_Arg, side + state - 0x82);
		else if (state == 20)
			continue;
		else
			flags |= state;
	}
	return flags == 15 || flags == 8;
}

/*
 * Will ask to a slighty simpler question: if the dialog is enlarged, will this
 * control also be enlarged ?
 */
static Bool SIT_CanResize(SIT_Widget w, int side)
{
	SIT_Widget s;
	int flags;

	if (! w->sw_Visible) return False;
	for (flags = 0, s = w; s && side < 4; )
	{
		SIT_Attach * a = &s->sw_Attachment[side];
		if (! relative[a->sa_Type])
		{
			if (a->sa_Type == SITV_AttachForm || (w != s && a->sa_Type == SITV_AttachNone))
				flags |= (side >> 1) + 1;
			side += 2;
			s = w;
		}
		else s = (APTR) a->sa_Arg;
	}
	return flags == 3;
}

/*
 * Core layout engine - compute widget positions
 */
static Bool SIT_LayoutWidget(SIT_Widget root, SIT_Widget w, int side, int adjust)
{
	SIT_Widget   s; /* sibling */
	SIT_Attach * a;
	int *        p; /* positions */
	int          j; /* reverse order */
	int          i, m1, m2, sz, off;
	Bool         rel;
	int          csz = (adjust == FitUsingOptimalBox ? &w->sw_OptimalBox.cx : &w->sw_CurrentBox.cx)[side];

	/* AttachNone and AttachNoOverlap must be computed after any other kind */
	j = w->sw_Attachment[side].sa_Type == SITV_AttachNone ||
	    w->sw_Attachment[side].sa_Type == SITV_AttachNoOverlap ? -2 : 2;

	/* Margins */
	m1 = root->sw_Margins[side];
	m2 = root->sw_Margins[side + 2];
	sz = (&root->sw_Box.width)[side] - (&root->sw_Box.x)[side];
	if (root->sw_Flags & SITF_NCMargins) {
		sz -= m1 + m2, m1 = m2 = 0;
		if (sz < 0) sz = 0;
	}
	rel = False;

	/* Handle horizontal and vertical constraints */
	for (i = side + (j < 0 ? 2 : 0), p = &w->sw_Box.x + i, s = NULL; 0 <= i && i < 4; i += j, p += j)
	{
		a = w->sw_Attachment + i;

		if (relative[a->sa_Type] && (s = (SIT_Widget) a->sa_Arg) && (s->sw_Visible & 0x7f) == 0) {
			*p = (&s->sw_Box.x)[i]; continue;
		}

		switch (a->sa_Type) {
		case SITV_AttachFixed: /* Let it where user set it */
			*p = (&w->sw_Fixed.x)[i] + a->sa_Offset + m1;
			if (i > 1) *p += (&w->sw_Box.x)[side];
			break;
		case SITV_AttachForm: /* Edge of container */
			off = a->sa_Offset;
			if (i > 1)
			{
				root->sw_Flags |= SITF_Style1Changed << side;
				if (off < 0) {
					if (- off >= m2) m2 = off = 0;
					else m2 += off, off = 0;
				}
				*p = sz - m2 - off;
			}
			else
			{
				if (off < 0) {
					if (- off >= m1) m1 = off = 0;
					else m2 += off, off = 0;
				}
				*p = m1 + off;
			}
			break;
		case SITV_AttachPosition: /* % of container */
			root->sw_Flags |= SITF_Style1Changed << side;
			off = a->sa_Offset;
			if (a->sa_Offset == SITV_OffsetCenter)
			{
				*p = ((sz * a->sa_Arg) >> 16) - csz / 2;
			}
			else if (i > 1)
			{
				if (off < 0) {
					if (- off >= m2) m2 = off = 0;
					else m2 += off, off = 0;
				}
				*p = ((sz * a->sa_Arg) >> 16) - m2 - off;
			}
			else
			{
				if (off < 0) {
					if (- off >= m1) m1 = off = 0;
					else m2 += off, off = 0;
				}
				*p = ((sz * a->sa_Arg) >> 16) + m1 + off;
			}
			break;
		case SITV_AttachWidget: /* Relative to another widget */
			if (i > 1) *p = (&s->sw_Box.x)[i - 2] - a->sa_Offset;
			else       *p = (&s->sw_Box.x)[i + 2] + a->sa_Offset;
			rel = True;
			break;
		case SITV_AttachOpposite: /* Relative to opposite border it used to */
			s = (SIT_Widget) a->sa_Arg;
			*p = a->sa_Offset + (&s->sw_Box.x)[i];
			rel = True;
			break;
		case SITV_AttachMiddle: /* Relative to middle of another widget */
			s = (SIT_Widget) a->sa_Arg;
			off = (i > 1 ? i - 2 : i);
			*p = ((&s->sw_Box.x)[off] + (&s->sw_Box.x)[off + 2] - csz) / 2;
			break;
		case SITV_AttachNone: /* Let it where it is */
		case SITV_AttachNoOverlap: /* Mix between None and Widget */
			if (i > 1) *p = p[-2] + csz;
			else       *p = p[ 2] - csz;
			if (a->sa_Type != SITV_AttachNoOverlap) break;

			s = (SIT_Widget) a->sa_Arg;
			if (i > 1)
			{
				off = (&s->sw_Box.x)[i - 2] - a->sa_Offset;
				if (*p > off) off = *p - off, *p -= off, p[-2] -= off;
			} else {
				off = (&s->sw_Box.x)[i + 2] + a->sa_Offset;
				if (*p < off) off -= *p, *p += off, p[2] += off;
			}
			rel = True;
		}
	}
	if (HAS_EVT(w, SITE_OnGeometrySet)) {
		/* Custom callback for setting geometry */
		int sz[] = {(&w->sw_Box.x)[side], (&w->sw_Box.x)[side+2], side};
		sz[1] -= sz[0];
		if (SIT_ApplyCallback(w, sz, SITE_OnGeometrySet))
			(&w->sw_Box.x)[side] = sz[0], (&w->sw_Box.x)[side+2] = sz[0] + sz[1];
	}
	/* Do not set width/height below a minimum */
	p = &w->sw_Box.x + side;

	/* Check if container box has to be enlarged */
	int * cside = &root->sw_Box.width + side;
	Bool  ret   = FALSE;

	if (p[0] < m1) {
		if (rel && adjust) ret = TRUE, *cside += m1 - p[0];
		else p[2] += m1 - p[0], p[0] = m1;
	}
	if (adjust == KeepDialogSize && ! (root->sw_Flags & (side?SITF_ReflowH:SITF_ReflowW))) return FALSE;
	// i = w->sw_Flags & (SITF_FixedWidth << side) ? (&w->sw_Fixed.width)[side] : (&box->cx)[side];
	a = &w->sw_Attachment[2+side];
	if (relative[a->sa_Type] && (s = (SIT_Widget)a->sa_Arg) && (s->sw_Visible & 0x7f))
		off = (&s->sw_Box.x)[a->sa_Type == SITV_AttachOpposite ? side+2 : side] - a->sa_Offset;
	else
		off = *cside - m2;
	if (p[2] > off) ret = TRUE, *cside += p[2] - off;
	if (p[2] - p[0] < csz && SIT_CanMove(w, side)) ret = TRUE, *cside += csz - (p[2] - p[0]);

	return ret;
}

HWND SIT_GetContainer(SIT_Widget w)
{
	for (w = w->sw_Parent; w && ! (w->sw_Type == SIT_DIALOG || w->sw_Type == SIT_TAB ||
	     w->sw_Type == SIT_CANVAS); w = w->sw_Parent);

	return w ? w->sw_Handle : NULL;
}

/*
 * A control is hidden, but other controls still have a reference to it. Mark it
 * as "visible" to compute its box, so that it can be assigned to the relatives.
 */
static int SIT_AutoVisible(SIT_Widget w)
{
	SIT_Attach * a;
	int          i, nb;
	for (i = nb = 0, a = w->sw_Attachment; i < 4; i ++, a++)
	{
		if (! relative[a->sa_Type]) continue;
		SIT_Widget s = (APTR) a->sa_Arg;
		if (s && ! s->sw_Visible) s->sw_Visible = 0x80, nb += SIT_AutoVisible(s) + 1;
	}
	return nb;
}

/* Children's box have been setup, calc their pos on dialog */
static int SIT_LayoutChildren(SIT_Widget root, int total, int mode)
{
	SIT_Widget list;
	int        count, i, init, loop;

	/* Apply maxWidth constraints */
	for (list = HEAD(root->sw_Children), count = 0; list; NEXT(list))
	{
		if ((list->sw_Max.ln_Prev == NULL && list->sw_Max.ln_Next == NULL) ||
		    (list->sw_Flags & SITF_Style1Changed) || ! list->sw_Visible) continue;

		SIT_Widget w, n;
		int box = (mode == FitUsingOptimalBox ?
			offsetof(struct SIT_Widget_t, sw_OptimalBox.cx) :
			offsetof(struct SIT_Widget_t, sw_CurrentBox.cx));

		#define CXVAL(w)    (* (LONG *) (((STRPTR)w) + box))
		for (w = list; w->sw_Max.ln_Prev; w = (APTR) w->sw_Max.ln_Prev);
		for (n = w, i = 0; w; w = (APTR) w->sw_Max.ln_Next) {
			if (! w->sw_Visible) continue;
			if (i < CXVAL(w)) i = CXVAL(w);
			list->sw_Flags |= SITF_Style1Changed;
			count ++;
		}
		for (w = n; w; CXVAL(w) = i, w = (APTR) w->sw_Max.ln_Next);
	}

	/* Clear flags */
	if (count > 0)
		for (list = HEAD(root->sw_Children); list; list->sw_Flags &= ~SITF_Style1Changed, NEXT(list));

	#ifdef DEBUG_GEOM
	fprintf(stderr, "====== Begin Layout Widgets of '%S' (%dx%dx%dx%d): %s, min: [%ld, %ld] total: %d =====\n", root->sw_Name, root->sw_Box.x, root->sw_Box.y, root->sw_Box.width, root->sw_Box.height, "Keep\0   Optimal\0Current\0Initial" + mode*8, root->sw_MinBox.cx, root->sw_MinBox.cy, total);
	#endif

	/* Perform 2 steps : one for horizontal constraints, a second for vertical */
	for (i = 0; i < 2; i ++)
	{
		redo_from_start:
		root->sw_Flags &= ~SITF_StylesChanged;
		for (init = 0, list = HEAD(root->sw_Children); list; NEXT(list))
		{
			ULONG flags = (SITF_FixedX | SITF_FixedWidth) << i;

			if (! list->sw_Visible) continue;
			if (i == 0) list->sw_Box.width  = list->sw_Fixed.width  + (list->sw_Box.x = list->sw_Fixed.x);
			else        list->sw_Box.height = list->sw_Fixed.height + (list->sw_Box.y = list->sw_Fixed.y);
			list->sw_Flags &= ~ (SITF_GeometrySet | SITF_GeometryChanged);
			if ((list->sw_Flags & flags) == flags)
				list->sw_Flags |= SITF_GeometrySet, init ++;
		}

		for (loop = init; loop < total; loop += count)
		{
			for (count = 0, list = HEAD(root->sw_Children); list; NEXT(list))
			{
				static STRPTR side[] = {"left", "top", "right", "bottom"};

				SIT_Attach * a = list->sw_Attachment + i; /* Left / top */
				SIT_Widget   w = (SIT_Widget) a->sa_Arg;

				/* Already done */
				if (! list->sw_Visible || (list->sw_Flags & (SITF_TopLevel|SITF_GeometrySet)))
					continue;

				if (relative[a->sa_Type])
				{
					if (w == NULL)
						SIT_Log(SIT_CRITICAL, "Missing relative %s control for '%S'", side[i], list->sw_Name);
					/* There is dependency with a widget, but priority has not yet been determined */
					if ((w->sw_Flags & SITF_GeometrySet) == 0) continue;
				}

				a += 2; /* Right / bottom */
				w = (SIT_Widget) a->sa_Arg;

				if (relative[a->sa_Type])
				{
					if (w == NULL)
						SIT_Log(SIT_CRITICAL, "Missing relative %s control for '%S'", side[i+2], list->sw_Name);
				    if ((w->sw_Flags & SITF_GeometrySet) == 0) continue;
				}

				list->sw_Flags |= SITF_GeometrySet;
				if (SIT_LayoutWidget(root, list, i, mode) && (root->sw_Flags & (SITF_Style1Changed << i)))
					/* Container box changed, need to recompute everything */
					goto redo_from_start;

				#ifdef DEBUG_GEOM
				fprintf(stderr, "%c Widget '%S' %p geometry : %dx%d - %dx%d (%dx%d) [%d]\n",
					i ? '|' : '-', list->sw_Name, list, list->sw_Box.x, list->sw_Box.y, list->sw_Box.width,
					list->sw_Box.height, list->sw_Box.width - list->sw_Box.x,
					list->sw_Box.height - list->sw_Box.y, (&root->sw_Box.width)[i]);
				#endif

				count ++;
			}

			/* Ooops, dependency loop */
			if (count == 0 && total > 0) return 0;
		}
		/* Root box gets enlarged only in SIT_LayoutWidget. if total == 0, it will never get here */
		if (total == 0 && (&root->sw_Box.width)[i] <= 0)
			(&root->sw_Box.width)[i] += root->sw_Margins[i] + root->sw_Margins[i + 2];
	}

	/* Clear AutoVisible flag */
	for (list = HEAD(root->sw_Children), count = 0; list; list->sw_Visible &= 0x7f,
	     list->sw_Flags &= ~SITF_GeomNotified, NEXT(list));

	root->sw_ChildBox.cx = root->sw_Box.width  - root->sw_Box.x;
	root->sw_ChildBox.cy = root->sw_Box.height - root->sw_Box.y;

	return 1;
}

/* Compute optimal width and height for dialog : used to constraint resizing */
static int SIT_LayoutOptimal(SIT_Widget root)
{
	SIT_Widget list;
	int        total;

	/* Resize capabilities: we need that information very early */
	root->sw_Flags &= ~(SITF_CanResizeW | SITF_CanResizeH);
	if (root->sw_Type == SIT_DIALOG)
	{
		for (list = HEAD(root->sw_Children); list; NEXT(list))
		{
			/* Size box has a form attachment on its bottom and right side and is marked as SITF_Internal */
			if (list->sw_Flags & (SITF_Internal|SITF_TopLevel)) continue;
			SIT_Attach * a = list->sw_Attachment;
			if (a->sa_Type == SITV_AttachPosition) root->sw_Flags |= SITF_CanResizeW; a++;
			if (a->sa_Type == SITV_AttachPosition) root->sw_Flags |= SITF_CanResizeH; a++;
			if (a->sa_Type == SITV_AttachForm || a->sa_Type == SITV_AttachPosition) root->sw_Flags |= SITF_CanResizeW; a ++;
			if (a->sa_Type == SITV_AttachForm || a->sa_Type == SITV_AttachPosition) root->sw_Flags |= SITF_CanResizeH;
		}
	}
	else /* Check if size of control is constrained */
	{
		if (SIT_CanMove(root, 0)) root->sw_Flags |= SITF_CanResizeW;
		if (SIT_CanMove(root, 1)) root->sw_Flags |= SITF_CanResizeH;
	}

	memset(&root->sw_Box, 0, sizeof root->sw_Box);

	if (root->sw_Type != SIT_DIALOG)
	{
		/*
		 * The main loop below will only compute size occupied by children. But for control
		 * like SIT_FRAME, its caption may require more space to not be clipped. This has to
		 * be done before, otherwise if we ever enlarge the root control, we will have to redo
		 * the whole procedure again.
		 */
		if (root->sw_Box.width  - root->sw_Box.x < root->sw_MinBox.cx)
			root->sw_Box.width  = root->sw_Box.x + root->sw_MinBox.cx;

		// XXX optimal height might be higher than initial height.
		// But tab control need this.
		if (root->sw_ResizePolicy == SITV_Fixed && root->sw_Box.height - root->sw_Box.y < root->sw_OptimalBox.cy)
			root->sw_Box.height = root->sw_Box.y + root->sw_OptimalBox.cy;
	}

	/* Reset children box dimension */
	for (list = HEAD(root->sw_Children), total = 0; list; NEXT(list))
	{
		if ((list->sw_Flags & SITF_TopLevel) || (list->sw_Visible & 0x7f) == 0) continue; total ++;

		memset(&list->sw_Box, 0, sizeof list->sw_Box);
		total += SIT_AutoVisible(list);

		if ((list->sw_OptimalBox.cx < 0 || list->sw_OptimalBox.cy < 0) && list->sw_OptimalWidth)
		{
			/* SIT_MinWidth or SIT_MinHeight set */
			SIZE pref = {};
			if (list->sw_MinBox.cx >= 0) pref.cx = list->sw_MinBox.cx;
			if (list->sw_MinBox.cy >= 0) pref.cy = list->sw_MinBox.cy;
			list->sw_OptimalWidth(list, &pref, NULL);
			list->sw_ChildBox = pref;

			if (pref.cx < list->sw_MinBox.cx) pref.cx = list->sw_MinBox.cx;
			if (pref.cy < list->sw_MinBox.cy) pref.cy = list->sw_MinBox.cy;
			list->sw_OptimalBox = pref;

			/* Some controls need explicit geometric constraint to have a width/height */
			if (list->sw_OptimalBox.cx < 0) list->sw_OptimalBox.cx = 0;
			if (list->sw_OptimalBox.cy < 0) list->sw_OptimalBox.cy = 0;
		}

		/* If frame/dialog is not resizable, optimal box will be like initial box */
		if (! SIT_CanResize(list, 0) && (list->sw_Flags & SITF_FixedWidth))
			list->sw_OptimalBox.cx = list->sw_Fixed.width;
		if (! SIT_CanResize(list, 1) && (list->sw_Flags & SITF_FixedHeight))
			list->sw_OptimalBox.cy = list->sw_Fixed.height;
	}

	SIT_LayoutChildren(root, total, FitUsingOptimalBox);

	/* SITV_Optimal resize policy will reset MinSize and MaxSize */
	if (root->sw_Type == SIT_DIALOG && ((SIT_Dialog)root)->sd_MinSize.cx == 0)
	{
		SIT_Dialog d = (APTR) root;
		RECT rect;
		SIT_AdjustWindowRect(d, &rect, NULL);
		d->sd_MinSize.cx = rect.right - rect.left;
		d->sd_MinSize.cy = rect.bottom - rect.top;
		d->sd_MaxSize.cx = (root->sw_Flags & SITF_CanResizeW ? 65535 : d->sd_MinSize.cx);
		d->sd_MaxSize.cy = (root->sw_Flags & SITF_CanResizeH ? 65535 : d->sd_MinSize.cy);
//		fprintf(stderr, "min '%S' = %ldx%ld, max = %ldx%ld\n", root->sw_Name, d->sd_MinSize.cx, d->sd_MinSize.cy, d->sd_MaxSize.cx, d->sd_MaxSize.cy);
	}
	root->sw_Flags &= ~ (SITF_GeometrySet | SITF_GeometryChanged | SITF_GeomNotified);
	return 0;
}

/*
 * - FitUsingOptimalBox: compute minimal size that dialog can be resized.
 * - KeepDialogSize: used when dialog is being resized by user and/or programmatically.
 *                   Possible reflow due to label/tab constraint.
 * - FitUsingCurrentBox: changes were performed on some controls (geometry, ctrl hidden /
 *                       removed, title, ...) which change its bounding box. Recompute position
 *                       of surrounding controls and adjust dialog size if required.
 * - FitUsingInitialBox: like OptimalBox, but use SIT_Width and SIT_Height information.
 */
static int SIT_LayoutInitial(SIT_Widget root, int mode)
{
	SIT_Widget list;
	int        count, total, offsetx, offsety;

	offsetx = offsety = 0;
	if (mode != KeepDialogSize)
	{
		offsetx = root->sw_Box.x;
		offsety = root->sw_Box.y;
		if (root->sw_Flags & SITF_GeometryChanged)
			root->sw_Box.width  -= offsetx, root->sw_Box.x = 0,
			root->sw_Box.height -= offsety, root->sw_Box.y = 0;
		else
			memset(&root->sw_Box, 0, sizeof root->sw_Box);

		/* User already set the size of container box */
		if (root->sw_Flags & SITF_FixedWidth)
			root->sw_Box.width  = root->sw_Box.x+MAX(root->sw_Fixed.width, root->sw_OptimalBox.cx);
		if (root->sw_Flags & SITF_FixedHeight)
			root->sw_Box.height = root->sw_Box.y+MAX(root->sw_Fixed.height, root->sw_OptimalBox.cy);
	}

	if (mode != KeepDialogSize && root->sw_Type != SIT_DIALOG)
	{
		if (root->sw_Box.width  - root->sw_Box.x < root->sw_MinBox.cx)
			root->sw_Box.width  = root->sw_Box.x + root->sw_MinBox.cx;

		if (root->sw_ResizePolicy == SITV_Fixed && root->sw_Box.height - root->sw_Box.y < root->sw_MinBox.cy)
			root->sw_Box.height = root->sw_Box.y + root->sw_MinBox.cy;
	}

	/* Reset children box dimension */
	for (list = HEAD(root->sw_Children), total = 0; list; NEXT(list))
	{
		if ((list->sw_Flags & SITF_TopLevel) || (list->sw_Visible & 0x7f) == 0) continue; total ++;

		memset(&list->sw_Box, 0, sizeof list->sw_Box);
		total += SIT_AutoVisible(list);

		switch (mode) {
		case FitUsingCurrentBox:
			if (list->sw_CurrentBox.cx > 0 && list->sw_CurrentBox.cy > 0 &&
			    ! (list->sw_Flags & SITF_GeomNotified)) break;
			// no break;
		case FitUsingInitialBox:
			if (list->sw_Children.lh_Count > 0)
			{
				if ((list->sw_CurrentBox.cx == 0 || list->sw_CurrentBox.cy == 0) || (list->sw_Flags & SITF_GeomNotified))
				{
					SIT_LayoutInitial(list, mode);
					list->sw_CurrentBox.cx = list->sw_Box.width  - list->sw_Box.x;
					list->sw_CurrentBox.cy = list->sw_Box.height - list->sw_Box.y;
					// XXX Reflow ? This two lines are for toolbar with children
					if (list->sw_MinBox.cx >= 0 && list->sw_CurrentBox.cx < list->sw_MinBox.cx) list->sw_CurrentBox.cx = list->sw_MinBox.cx;
					if (list->sw_MinBox.cy >= 0 && list->sw_CurrentBox.cy < list->sw_MinBox.cy) list->sw_CurrentBox.cy = list->sw_MinBox.cy;
					if (list->sw_Children.lh_Count == 0)
					{
						/* Problem with label and reflow */
						if (list->sw_CurrentBox.cx < list->sw_OptimalBox.cx)
							list->sw_CurrentBox.cx = list->sw_OptimalBox.cx;
						if (list->sw_CurrentBox.cy < list->sw_OptimalBox.cy)
							list->sw_CurrentBox.cy = list->sw_OptimalBox.cy;
					}
				}
				/* Avoid doing the same stuff again */
				list->sw_ChildBox = list->sw_CurrentBox;
			}
			else
			{
				/* Fixed height / width */
				if (list->sw_CurrentBox.cx <= 0)
					list->sw_CurrentBox.cx = list->sw_Flags & SITF_FixedWidth ? list->sw_Fixed.width : list->sw_OptimalBox.cx;
				if (list->sw_CurrentBox.cy <= 0)
					list->sw_CurrentBox.cy = list->sw_Flags & SITF_FixedHeight ? list->sw_Fixed.height : list->sw_OptimalBox.cy;
			}
			if (mode == FitUsingInitialBox)
			{
				SIZE pref = list->sw_ChildBox;
				if (list->sw_Flags & SITF_FixedWidth)  list->sw_CurrentBox.cx = list->sw_Fixed.width;
				if (list->sw_Flags & SITF_FixedHeight) list->sw_CurrentBox.cy = list->sw_Fixed.height;
				if (list->sw_Flags & SITF_FixedX)      list->sw_Attachment[0].sa_Type = SITV_AttachFixed;
				if (list->sw_Flags & SITF_FixedY)      list->sw_Attachment[1].sa_Type = SITV_AttachFixed;
				if ((list->sw_Flags & SITF_AutoHeight) && memcmp(&pref, &list->sw_CurrentBox, sizeof pref))
				{
					list->sw_OptimalWidth(list, &list->sw_CurrentBox, NULL);
				}
			}
			break;
		case KeepDialogSize:
			if (list->sw_CurrentBox.cx <= 0 && list->sw_CurrentBox.cy <= 0)
				list->sw_CurrentBox = list->sw_OptimalBox;
		}
	}

	for (count = 0; count < 10; count ++)
	{
		int reflow = 0;
		SIT_LayoutChildren(root, total, mode);
		root->sw_Flags &= ~ (SITF_ReflowH|SITF_ReflowW);

		/* Also check if children's children need reflow */
		for (list = HEAD(root->sw_Children), count = 0; list; NEXT(list))
		{
			list->sw_Visible &= 0x7f;
			if (! list->sw_Visible) continue;
			list->sw_CurrentBox.cx = list->sw_Box.width  - list->sw_Box.x;
			list->sw_CurrentBox.cy = list->sw_Box.height - list->sw_Box.y;

			if ((list->sw_Flags & SITF_AutoHeight) && memcmp(&list->sw_CurrentBox, &list->sw_ChildBox, sizeof (SIZE)))
			{
				SIZE old = list->sw_CurrentBox;
				list->sw_OptimalWidth(list, &list->sw_CurrentBox, NULL);
				if (old.cx != list->sw_CurrentBox.cx) reflow |= 1;
				if (old.cy != list->sw_CurrentBox.cy) reflow |= 2;
				// reflow = memcmp(&old, &list->sw_CurrentBox, sizeof old);
			}

			if (list->sw_Children.lh_Count == 0) continue;

			/*
			 * Optimization: some widget will already compute optimal box recursively. This
			 * will avoid numerous steps redoing the same thing all over again. Used by KeepDialogSize policy.
			 */
			if (memcmp(&list->sw_ChildBox, &list->sw_CurrentBox, sizeof (SIZE)))
			{
				// fprintf(stderr, "force recalc for '%S' %ld != %d\n", list->sw_Name, list->sw_ChildBox.cx, list->sw_Box.width - list->sw_Box.x);
				list->sw_Flags |= SITF_GeometryChanged;
				if (SIT_LayoutInitial(list, mode)) return 1;
				if (list->sw_ChildBox.cx != list->sw_CurrentBox.cx) {
					/* Widget size has changed, need to layout children again */
					if (list->sw_Flags & SITF_CanResizeW) reflow |= 1;
					/* Unless widget size is constrained, keep constrained size instead */
					else list->sw_Box.width = list->sw_Box.x + list->sw_CurrentBox.cx;
				}
				if (list->sw_ChildBox.cy != list->sw_CurrentBox.cy) {
					if (list->sw_Flags & SITF_CanResizeH) reflow |= 2;
					else list->sw_Box.height = list->sw_Box.y + list->sw_CurrentBox.cy;
				}
				list->sw_ChildBox.cx = list->sw_Box.width  - list->sw_Box.x;
				list->sw_ChildBox.cy = list->sw_Box.height - list->sw_Box.y;
				list->sw_CurrentBox = list->sw_ChildBox;
			}
		}
		if (! reflow) break;
		if (reflow & 1) root->sw_Box.x = root->sw_Box.width  = 0, root->sw_Flags |= SITF_ReflowW;
		if (reflow & 2) root->sw_Box.y = root->sw_Box.height = 0, root->sw_Flags |= SITF_ReflowH;
		// memset(&root->sw_Box, 0, sizeof root->sw_Box);
	}
	root->sw_Flags &= ~ (SITF_GeometrySet | SITF_GeometryChanged | SITF_GeomNotified);
	root->sw_Box.x += offsetx;
	root->sw_Box.y += offsety;
	root->sw_Box.width  += offsetx;
	root->sw_Box.height += offsety;
	return 0;
}

BOOL SIT_LayoutWidgets(SIT_Widget root, int mode)
{
	int count;
	if (mode >= FitUsingCurrentBox)
	{
		/* Need to compute OptimalBox first */
		SIT_LayoutOptimal(root);
		SIT_LayoutInitial(root, mode);
	}
	else if (mode == FitUsingOptimalBox)
		SIT_LayoutOptimal(root);
	else
		SIT_LayoutInitial(root, mode);

	// if (count == 10) return FALSE; /* Hmm, not good */

	#ifdef DEBUG_GEOM
	fprintf(stderr, "*** root size '%S' = %dx%d - %dx%d [%d-%d]\n", root->sw_Name, root->sw_Box.x, root->sw_Box.y,
		root->sw_Box.width - root->sw_Box.x, root->sw_Box.height - root->sw_Box.y, root->sw_Flags & SITF_CanResizeW?1:0,
		root->sw_Flags & SITF_CanResizeH?1:0);
	#endif

	/* Help user debug its attachments */
	if (verbose && root->sw_Type == SIT_DIALOG)
	{
		SIT_Widget list;
		#define  d  ((SIT_Dialog)root)
		fprintf(stderr, "DIALOG: %S %dx%d, min: %ldx%ld, max: %ldx%ld\n",
			root->sw_Name, root->sw_Box.width, root->sw_Box.height,
			d->sd_MinSize.cx, d->sd_MinSize.cy, d->sd_MaxSize.cx, d->sd_MaxSize.cy
		);
		#undef d
		for (count = 0, list = HEAD(root->sw_Children), count = 0; list; )
		{
			if (list->sw_Flags & (SITF_Internal|SITF_TopLevel)) { NEXT(list); continue; }

			fprintf(stderr, "%*s- %S = %dx%d - %dx%d (min: %ldx%ld) [%d-%d], tab:%d, cur:%ldx%ld\n", count, "", list->sw_Name,
				list->sw_Box.x, list->sw_Box.y, list->sw_Box.width - list->sw_Box.x, list->sw_Box.height - list->sw_Box.y,
				list->sw_MinBox.cx < 0 ? list->sw_OptimalBox.cx : list->sw_MinBox.cx,
				list->sw_MinBox.cy < 0 ? list->sw_OptimalBox.cy : list->sw_MinBox.cy,
				SIT_CanMove(list, 0), SIT_CanMove(list, 1), list->sw_TabOrder & 0xffff,
				list->sw_CurrentBox.cx, list->sw_CurrentBox.cy);

			if (list->sw_Children.lh_Count > 0)
			{
				count += 2;
				list = HEAD(list->sw_Children);
				continue;
			}
			else if (list->sw_Node.ln_Next == NULL && list->sw_Parent != root)
			{
				list = list->sw_Parent;
				count -= 2;
			}
			NEXT(list);
		}
	}
	return True;
}

#ifdef	DEBUG
void SIT_DumpGeom(SIT_Widget root)
{
	SIT_Widget list;
	int count, i;
	for (count = 0, list = HEAD(root->sw_Children), count = 0; list; )
	{
		fprintf(stderr, "%*s<%S", count, "", list->sw_Name);

		for (i = 0; i < 4; i ++)
		{
			static STRPTR attrs[] = {"left", "top", "right", "bottom"};
			static STRPTR attach[] = {"NONE", "FORM", "POSITION", "WIDGET", "OPPOSITE", "MIDDLE", "NOOVERLAP"};
			SIT_Attach * a = list->sw_Attachment + i;
			if (a->sa_Type == SITV_AttachNone) continue;
			fprintf(stderr, " %s=%s", attrs[i], attach[a->sa_Type]);
			if (relative[a->sa_Type])
			{
				SIT_Widget s = (APTR) a->sa_Arg;
				fprintf(stderr, ",%S", s ? s->sw_Name : L"(null)");
				if (a->sa_Offset > 0) fprintf(stderr, ",%d", a->sa_Offset);
			}
			else if (a->sa_Offset != 0)
			{
				fprintf(stderr, ",,%d", a->sa_Offset);
			}
		}
		if (list->sw_TabOrder > 0) fprintf(stderr, " tabOrder=%d", list->sw_TabOrder & 0Xffff);
		if (list->sw_TabOrder > 0xffff) fprintf(stderr, "(%d)", list->sw_TabOrder >> 16);
		fputs(">\n", stderr);

		if (list->sw_Children.lh_Count > 0)
		{
			count += 2;
			list = HEAD(list->sw_Children);
			continue;
		}
		else if (list->sw_Node.ln_Next == NULL && list->sw_Parent != root)
		{
			list = list->sw_Parent;
			count -= 2;
			fprintf(stderr, "%*s</%S>\n", count, "", list->sw_Name);
		}
		NEXT(list);
	}
}
#endif

/* Widget is about to be removed, remove any reference of this control within attachments */
Bool SIT_ReassignAttachments(SIT_Widget w)
{
	SIT_Attach * a;
	SIT_Widget   c;
	int          i, ret;

	for (c = HEAD(w->sw_Parent->sw_Children), ret = False; c; NEXT(c))
	{
		if (c == w) continue;
		for (i = 0, a = c->sw_Attachment; i < 4; i ++, a ++)
		{
			if (relative[a->sa_Type] == 0 || a->sa_Arg != (ULONG) w) continue;
			SIT_Widget r = (APTR) a->sa_Arg;
			memcpy(a, r->sw_Attachment + i, sizeof *a);
			ret = True;
		}
	}
	return ret;
}
